home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Meeting Pearls 4
/
Meeting Pearls Vol. IV (1996)(GTI - Schatztruhe)[!].iso
/
Pearls
/
dev
/
Language
/
CLisp
/
doc
/
affi.txt
next >
Wrap
Text File
|
1996-08-12
|
13KB
|
321 lines
The Amiga Foreign Function Call Facility
========================================
Another Foreign Function Interface
All symbols relating to the simple foreign function interface are
exported from the package AFFI. To use them, (USE-PACKAGE "AFFI").
Design issues
-------------
AFFI was designed to be small in size but powerful enough to use most
library functions. Lisp files may be compiled to .FAS files without the need
to load function definition files at run-time and without external C or
linker support. Memory images can be created, provided that the function
libraries are opened at run-time.
Therefore, AFFI supports only primitive C types (integers 8, 16 and 32 bits
wide, signed or unsigned, pointers) and defines no new types or classes.
Foreign functions are not first-class objects (you can define a lambda
yourself), name spaces are separate.
The AFFI does no tracking of resources. Use FINALIZE (see impnotes.txt).
Overview
--------
These are the AFFI forms:
(DECLARE-LIBRARY-BASE keyword-base library-name)
(REQUIRE-LIBRARY-FUNCTIONS library-name [(:import {string-names}*))
(OPEN-LIBRARY base-symbol)
(CLOSE-LIBRARY base-symbol)
(WITH-OPEN-LIBRARY (base-symbol | library-name) {forms}*)
(DEFFLIBFUN function-name base-symbol offset mask result-type {argument-type}*)
(DECLARE-LIBRARY-FUNCTION function-name library-name {options}*)
(FLIBCALL function-name {argument}*)
(MLIBCALL function-name {argument}*)
(MEM-READ address result-type [offset])
(MEM-WRITE address type value [offset])
(MEM-WRITE-VECTOR address vector [offset])
(NZERO-POINTER-P value)
Except for WITH-OPEN-LIBRARY, DECLARE-LIBRARY-FUNCTION and MLIBCALL,
everything is a function.
A library contains a collection of functions. The library is referred to by
a symbol referred as library-base at the AFFI level. This symbol is
created in the package AFFI. The link between this symbol and the OS-level
library name is established by DECLARE-LIBRARY-BASE. To avoid multiple
package conflicts, this and only this function requires the symbol-name to
be in the KEYWORD package. The function returns the library-base.
A library may be opened by OPEN-LIBRARY and closed by CLOSE-LIBRARY. An
opened library must be closed. WITH-OPEN-LIBRARY is provided to
automatically close the library for you, thus it's much safer to use.
A function is contained in a library. Every function is referred to by a
symbol. A function is defined through DEFFLIBFUN or DECLARE-LIBRARY-FUNCTION
by giving the function name, the library-base, an offset into the library, a
mask (or NIL) for register-based library calls, the result type and all
parameter-types. REQUIRE-LIBRARY-FUNCTIONS loads the complete set of
functions defined in a library file. Symbols are created in the package AFFI
and imported into the current package.
FLIBCALL and MLIBCALL call library functions. MLIBCALL is a macro that does
a few cheks at macroexpansion time and allows the compiler to inline the
call, not requiring the foreign function to be defined again at load or
execution time. The use of this macro is advertised wherever possible.
MEM-READ reads an arbitrary address (with offset for structure references)
and returns the given type.
MEM-WRITE writes an arbitrary address. MEM-WRITE-VECTOR copies the content
of a LISP string or unsigned-byte vector into memory.
NZERO-POINTER-P tests for non-NULL pointers in all recognized
representations (NULL, UNSIGNED-BYTE and FOREIGN-POINTER).
Foreign Libraries
-----------------
DECLARE-LIBRARY-BASE ought to be wrapped in an EVAL-WHEN (COMPILE EVAL LOAD)
form and come before any function is referenced, because the library base
symbol must be known.
OPEN-LIBRARY tries to open the library referenced by the base symbol.
Therefore it must have been preceeded with DECLARE-LIBRARY-BASE. The call
returns NIL on failure. OPEN-LIBRARY calls nest. Every successful call must
be matched by CLOSE-LIBRARY. WITH-OPEN-LIBRARY does this for you and also
allows you to specify the library by name, provided that its base has been
declared. It is recommended to use this macro and to reference the library
by name.
CLISP will not close libraries for you at program exit. [A previous version
did so but now AFFI is a module and there are no module exit functions.]
Programmers, watch AFFI::*LIBRARIES-ALIST*.
(Foreign) C types
-----------------
The following foreign C types are used in AFFI. They are *not* regular
Common Lisp types or CLOS classes.
AFFI name Lisp equiv C equiv
NIL NIL void (r)
4 (UNSIGNED-BYTE 32) unsigned long
2 (UNSIGNED-BYTE 16) unsigned short
1 (UNSIGNED-BYTE 8) unsigned char
-4 (SIGNED-BYTE 32) long
-2 (SIGNED-BYTE 16) short
-1 (SIGNED-BYTE 8) signed char
0 (MEMBER NIL T) "BOOL" (r)
* opaque void*
:EXTERNAL opaque void*
STRING STRING or VECTOR char*
:IO STRING or VECTOR char*
(r) as a result type for functions only.
Objects of type STRING are copied and passed NUL-terminated on the execution
stack. On return, a LISP string is allocated and filled from the address
returned (unless NULL). Functions with :IO parameters are passed the address
of the Lisp STRING or UNSIGNED BYTE-VECTOR. These are not NUL-terminated!
This is useful for functions like like read() which do not need an array at
a constant address longer than the dynamic extent of the call (it is
dangerous to define callback functions with :IO (or STRING) type
parameters). Arguments of type INTEGER and FOREIGN-POINTER are always
acceptable where a STRING or :IO type is specified.
To meet the design goals, predefined types and objects were used. As such,
pointers were represented as integers. Now that there is the FOREIGN-POINTER
type, both representations may be used on input. The pointer type should be
therefore considered as opaque. Use NZEROP-POINTER-P for NULL tests.
Foreign functions
-----------------
Foreign Functions are declared either through DEFFLIBFUN or
DECLARE-LIBRARY-FUNCTION. The former is closer to the low-level
implementation of the interface, the latter is closer to the other FFI.
DEFFLIBFUN requires the library base symbol and register mask to be
specified, DECLARE-LIBRARY-FUNCTION requires the library name and computes
the mask from the declaration of the arguments.
The value of mask is implementation-dependent. On the Amiga, it's an integer
whose hexadecimal value is the reverse of the function argument register
numbers, where D0 has number 1 and A6 number #xF. A NIL mask is reserved for
stack-based calls (unimplemented).
The AFFI type `0' is only acceptable as a function result type and yields
either T or NIL. The difference between * and :EXTERNAL is the following: *
uses integers, :EXTERNAL uses FOREIGN-TYPE as function result-type (except
from NIL for a NULL pointer) and refuses objects of type STRING or UNSIGNED
BYTE-VECTOR as input. Thus :EXTERNAL provides some security on the input and
the ability to use FINALIZE for resource-tracking on the output side.
(DECLARE-LIBRARY-FUNCTION name library-name {options}*)
option ::=
(:offset <library-offset>)
| (:arguments {(<arg-name> <AFFI type> <register>)}*)
| (:return-type <AFFI-type>)
register ::= :D0 :D1 .. :D7 :A0 ... :A6
declares a named library funtion for further reference through
FLIBCALL and MLIBCALL.
MLIBCALL should be the preferred way of calling foreign functions (when they
are known at compile-time) as macroexpansion-time checks may be performed
and the call can be sort of inlined.
Memory access
-------------
(MEM-READ address type offset) can read 8, 16 and 32 bit signed or unsigned
integers (AFFI types -4, -2, -1, 1, 2 ,4), a pointer (*), a NUL-terminated
string (STRING) or, if the type argument is of type STRING or UNSIGNED
BYTE-VECTOR, it can fill this vector. :EXTERNAL is not an acceptable type as
no object can be created by using MEM-READ.
(MEM-WRITE address type value [offset]) writes integers (AFFI type -4, -2,
-1, 1, 2 and 4) or pointer values (type *), but not vectors to the specified
memory address.
(MEM-WRITE-VECTOR address vector [offset]) can write memory from the given
vector (of type STRING or UNSIGNED BYTE-VECTOR).
Function Definition Files
-------------------------
REQUIRE-LIBRARY-FUNCTION will REQUIRE a file of name derived from the
library name and with type "affi". It may be used to import all names into
the current package or only a given subset identified by string names, using
the :IMPORT keword (recommended use). Some definition files for standard
Amiga libraries are provided. See example 1 below.
As REQUIRE-LIBRARY-FUNCTIONS loads a global file which you, the programmer,
may have not defined, you may consider declaring every function yourself to
be certain what the return and argument types are. See example 4 below.
The file READ-FD.LSP defines the function MAKE-PARTIAL-FD-FILE with which
the provided ".affi" files have been prepared from the original Amiga FD
files (located in the directory FD:). They must still be edited as the
function cannot know whether a function accepts a *, :IO, STRING or
:EXTERNAL argument and because files in FD: only contain a register
specification, not the width of integer arguments (-4, -2, -1, 1, 2, or 4).
Hints
-----
By using appropriate EVAL-WHEN forms for DECLARE-LIBRARY-BASE and
REQUIRE-LIBRARY-FUNCTIONS and not using FLIBCALL, it is possible to write
code that only loads library function definition files at compile-time. See
example 1 below.
Do not rely on FINALIZE to free resources for you, as CLISP does not call
finalizers when it exits, use UNWIND-PROTECT.
Caveats
-------
You can consider the library bases being symbols in need of being imported
from the package AFFI originating from a brain-damage, causing the usual
symbol headaches when using foreign functions calls within macros. Luckily,
even if the high-level interface (or its implementation in AFFI1.LSP) were
to change, the low-level part (AFFI.D) should remain untouched as all it
knows are integers and foreign-pointers, no symbols. The difficulty is just
to get the library base value at run-time. Feel free to suggest enhancements
to this facility!
Examples
--------
NB: These examples are soemwhat specific to the Amiga.
1. Using a predefined library function file
(use-package "AFFI")
;; SysBase is the conventional name for exec.library
;; It is only enforced by the file loaded by REQUIRE-LIBRARY-FUNCTIONS
(eval-when (compile eval load)
(declare-library-base :SysBase "exec.library")) ;keyword avoids name conflicts
;; using only MLIBCALL allows not to load definitions at load-time
(eval-when (compile eval)
(require-library-functions "exec.library"
:import '("FindTask")))
(with-open-library ("exec.library")
(print (mlibcall FindTask 0))
)
This file can be used in interpreted and compiled mode. Compiled, it will
have inlined the library function calls.
2. Using flibcalll
(use-package "AFFI")
(eval-when (compile eval load)
(declare-library-base :SysBase "exec.library")) ;keyword avoids name conflicts
;; The load situation permits the use of flibcall
(eval-when (eval compile load)
(require-library-functions "exec.library"))
(unless (open-library 'SysBase) (error "No library for SysBase"))
(flibcall (if t 'FindTask 'Debug) 0)
(close-library 'SysBase)
3. Be fully dynamic, defining library bases ourselves
(use-package "AFFI")
(eval-when (compile eval load)
(defvar mylib (declare-library-base :foobase "foo.library")))
(eval-when (eval compile load) ;eval allows mlibcall, load flibcall
(defflibfun 'foo1 mylib -30 '#xA '* 'string)
(defflibfun 'foo2 mylib -36 '#x21 0 * 4))
(defun foo (name)
(when (open-library mylib)
(list (mlibcall foo1 name) (flibcall 'foo2 name 123213))
(close-library mylib)))
4. Some sample function definitions
(defflibfun 'FindTask 'SysBase -294 #xA '* 'string)
(declare-library-function FindTask "exec.library"
(:offset -294)
(:return-type *)
(:arguments
(name string :A1)))
(declare-library-function NameFromLock "dos.library"
(:offset -402)
(:return-type 0)
(:arguments
(lock 4 :D1)
(buffer :io :D2)
(len 4 :D3)))
(eval-when (compile eval)
(defconstant GVF_LOCAL_ONLY (ash 1 9))
(defflibfun 'SetVar 'DosBase -900 #x5432 0 'string 'string -4 4))
(defun setvar (name value)
(with-open-library (DosBase)
;; length of -1 means find length of NUL-terminated-string
(mlibcall SetVar name value -1 GVF_LOCAL_ONLY)))